package org.ihelpuoo.orm;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPathExpressionException;

import org.apache.log4j.Logger;
import org.ihelpuoo.orm.filter.IdsSQLObjectFilter;
import org.ihelpuoo.orm.filter.Filter;
import org.ihelpuoo.orm.filter.SegmentObjectFilter;
import org.ihelpuoo.orm.filter.FieldFilter;
import org.ihelpuoo.orm.filter.SQLFilter;
import org.ihelpuoo.db.sql.SQLUtils;
import org.ihelpuoo.lang.ExceptionUtils;
import org.ihelpuoo.lang.ListUtils;
import org.ihelpuoo.lang.ObjectUtils;
import org.ihelpuoo.lang.StringUtils;
import org.ihelpuoo.parser.segment.CharSegment;
import org.ihelpuoo.parser.segment.Segment;
import org.ihelpuoo.parser.segment.SegmentUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class ObjectRule
{
	private static Logger	log	= Logger.getLogger(ObjectRule.class);

	static ObjectRule newInstance(III factory, Document doc) throws IIIException
	{
		ObjectRule r = new ObjectRule(factory,null);
		r.init(doc);
		return r;
	}

	static ObjectRule newInstance(III factory, Class type) throws IIIException
	{
		if (null == type)
			throw new IIIException("Can not create ObjectRule for NULL");
		ObjectRule r = new ObjectRule(factory,type);
		r.init(null);
		return r;
	}

	private ObjectRule(III iii, Class type)
	{
		this.config = (IIIConfig)iii.getConfig();
		this.iii = iii;
		this.setType(type);
	}

	III								iii;
	private Class					type;
	private Segment					tableName;
	private Segment					tableWriteName;
	private String					tagName;
	private Map<Field, FieldRule>	mapping;
	Element							root;
	IIIConfig						config;
	private boolean					isDbDone;
	private boolean					isDbEvaluing;
	Field							keyField;
	private FieldRule[]				componentFields;
	private FieldRule[]				autoIncrements;
	private FieldRule[]				pks;
	private boolean					ignoreNull;
	private String					name;
	private FieldRule[]				newKeys;

	/**
	 * @return Class of the object rule.
	 */
	public Class getType()
	{
		return type;
	}

	private void setType(Class type)
	{
		this.type = type;
		if (this.type != null)
			this.name = this.config.evalObjectName(type);
	}

	/**
	 * It will return relative table name of Object. If this rule didn't indicate the
	 * table name clearly (in "table" attribute of root element), it will GUESS the table
	 * name by taking the object simple name (Class.getSimpleName()) and change it to lowercase
	 * <br>
	 * For instance, if the current type is <b>org.ihelpuoo.fake.CatBean</b>
	 * the table name will be <b>catbean</b>. 
	 * <p>
	 * Sometimes, client don't wanna keep some common suffix (just like "bean","descriptor")
	 * then it can delcare those suffix in propery "object-suffix" of <b>ObjectFactory</b>.
	 * <br>
	 * For instance, if the current type is <b>org.ihelpuoo.fake.CatBean</b> and "object-suffix"
	 * is "bean,descriptor", then the table name will be <b>cat</b>
	 * <p>
	 * Another requirement of the table name is add a prefix to the table name.
	 * Fox instance, client maybe wanna add "t_" before each table in DB. We can define
	 * it by property "table-prefix" in <b>ObjectFactory</b> also.
	 * <br>
	 * For instance, if object name <b>org.ihelpuoo.fake.CatBean</b> and "object-suffix"
	 * is "bean,descriptor", and "table-prefix" is "t_" then the table name will be <b>t_cat</b>
	 * <p>
	 * Somestimes, client wanna support dynamic table name. If that, client must indicate the table
	 * name in "table" attirbute of root element. When call this method param "obj" is required.
	 * <br>
	 * For instance, if object name <b>org.ihelpuoo.fake.CatBean</b> and "object-suffix"
	 * is "bean,descriptor", and "table-prefix" is "t_", define "table" attribute in object rule
	 * config file as table="${:prefix}${:name}_${id}". Set the param "refer" as an object with
	 * "id" attribute and getId() method, return 34 (Integer). Then the table name will be
	 * <b>t_cat_34</b>
	 * <br>
	 * Obviously, it will replace PlugPoint ${id} by 34. replace PlugPoint ${:prefix} and ${:name}
	 * by table prefix "t_" and object name "cat".  
	 * 
	 * @see org.ihelpuoo.parser.segment.Segment
	 * 
	 * @param refer
	 * the referance object, if the table name is dynamic, it will fill the PlugPoint by
	 * obj's attribute. (getter of the attribute is required if disaccessable)
	 * 
	 * @return
	 * 		table name in database
	 */
	public String getTableName(Object refer)
	{
		if (tableName == null)
		{
			String tn;
			if (null != root && root.hasAttribute("table"))
				tn = root.getAttribute("table");
			else
			{
				tn = StringUtils.lowercaseWordBy(config.peelOffSuffix(type.getSimpleName()),'_');
				if (null != config && !StringUtils.isBlank(config.getTablePrefix()))
					tn = config.getTablePrefix() + tn;
			}
			tableName = new CharSegment(tn);
		}
		if (tableName.size() == 0)
			return tableName.getOriginContent();
		Segment seg = SegmentUtils.fillByObject(tableName.duplicate(),refer);
		seg.set(":name",name);
		if (!StringUtils.isBlank(config.getTablePrefix()))
			seg.set(":prefix",config.getTablePrefix());
		return seg.toString();
	}

	public String getTableWriteName(Object refer)
	{
		if (tableWriteName == null)
		{
			if (null != root && root.hasAttribute("table-write"))
				tableWriteName = new CharSegment(root.getAttribute("table-write"));
			else
				tableWriteName = tableName;
			if (null == tableWriteName)
				return this.getTableName(refer);
		}
		if (tableWriteName.size() == 0)
			return tableWriteName.getOriginContent();
		Segment seg = SegmentUtils.fillByObject(tableWriteName.duplicate(),refer);
		seg.set(":name",name);
		if (!StringUtils.isBlank(config.getTablePrefix()))
			seg.set(":prefix",config.getTablePrefix());
		return seg.toString();
	}

	/**
	 * It will return relative tag name of Object. If this rule didn't indicate the
	 * tag name clearly (in "tag" attribute of root element), it will GUESS the tag
	 * name as take the object simple name (Class.getSimpleName()) and change it to lowercase
	 * <br>
	 * For instance, if the current type is <b>org.ihelpuoo.fake.CatBean</b>
	 * the tag name will be <b>catbean</b>. 
	 * <p>
	 * Sometimes, client don't wanna keep some common suffix (just like "bean","descriptor")
	 * then it can delcare those suffix in propery "object-suffix" of <b>ObjectFactory</b>.
	 * <br>
	 * For instance, if the current type is <b>org.ihelpuoo.fake.CatBean</b> and "object-suffix"
	 * is "bean,descriptor", then the tag name will be <b>cat</b>
	 * 		    
	 * @return
	 * 		tag name in XML
	 */
	public String getTagName()
	{
		if (tagName == null)
		{
			synchronized (this)
			{
				if (tagName == null)
					if (null != root && root.hasAttribute("tag"))
						tagName = root.getAttribute("tag");
					else
						tagName = getName();
			}
		}
		return tagName;
	}

	/**
	 * The root element of the Document must has an attribute to
	 * indicate the object full class. 
	 * 
	 * @param doc
	 * @throws IIIException
	 */
	void init(Document doc) throws IIIException
	{
		if (null != doc)
		{
			root = doc.getDocumentElement();
			try
			{
				setType(Class.forName(root.getAttribute("type")));
			}
			catch (ClassNotFoundException e)
			{
				throw new IIIException(e);
			}
		}
		this.initMappings(type);
	}

	/**
	 * If database part wihtout int, it will init it.
	 * 
	 * @param type: Object type
	 * @param conn: Connection object
	 * @param obj: A refer object, required when the table is dynamice.
	 * @throws IIIException
	 */
	public void checkDatabaseConfig(Connection conn, Object refer) throws IIIException
	{
		if (isDbDone || isDbEvaluing)
			return;
		isDbEvaluing = true;
		Statement st = null;
		ResultSet rs = null;
		try
		{
			st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
			st.setFetchSize(1);
			rs = st.executeQuery("SELECT * FROM " + this.getTableName(refer));
			Field[] fs = ObjectUtils.getAllMyStdFields(this.type);
			try
			{
				for (int i = 0; i < fs.length; i++)
				{
					Field f = fs[i];
					FieldRule r = this.getFieldRule(f);
					if (null == r)
					{
						throw new IIIException("init db rule field rule \"" + f.getName()
								+ "\" is null");
					}
					// evalue by config file if it existed
					r.evalDbPart(f,root,rs);
				}
				if (!this.isHasPKs())
					this._guessPKs();
				// check each PK fields.
				FieldRule[] pks = this.getPKs();
				for (int i = 0; i < pks.length; i++)
					this._checkPKField(pks[i]);

				// evaluate each component fields
				guessComponentFields(conn,refer);

				// make DB evaluation is done
				isDbDone = true;
				isDbEvaluing = false;
				/* check all my componentFieldfilter */
			}
			catch (XPathExpressionException e)
			{
				throw new IIIException(e);
			}
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
		finally
		{
			try
			{
				if (null != rs)
					rs.close();
				if (null != st)
					st.close();
			}
			catch (SQLException e)
			{
				throw new IIIException(e);
			}
		}
	}

	private void guessComponentFields(Connection conn, Object refer) throws IIIException
	{
		// guess filter for each component field
		FieldRule[] coms = this.getComponentFields();
		for (int i = 0; i < coms.length; i++)
		{
			FieldRule r = coms[i];
			String fieldName = r.getField().getName();
			// get sub type
			Class subClass = r.getComponentType();

			// make sure sub rule is evaluated.
			ObjectRule subRule = iii.manual.getRule(subClass);
			subRule.checkDatabaseConfig(conn,refer);

			if (r.hasRelation())
				continue;

			//: Object
			if (r.isObject())
			{
				// first obj{dept : - deptId -> DeptObject.id}
				String fldNm = StringUtils.plural2singular(fieldName) + "Id";
				if (!ObjectUtils.hasField(type,fldNm))
					fldNm = fldNm.toLowerCase();
				if (ObjectUtils.hasField(type,fldNm) && ObjectUtils.hasField(subClass,"id"))
					r.setRelation(fldNm,"id");

				// second HhPpObj.id -> XyzObj.hhPpId,
				if (null == r.getFilter() && ObjectUtils.hasField(type,"id"))
				{
					fldNm = this.getName() + "Id";
					if (!ObjectUtils.hasField(subClass,fldNm))
						fldNm = fldNm.toLowerCase();
					if (ObjectUtils.hasField(subClass,fldNm))
						r.setRelation("id",fldNm);
				}
			}
			//: Array | List | Map
			else
			{
				if (this.isHasPKs() && this.getPKs()[0].getName().equals("id"))
				{
					String fldNm = this.getName() + "Id";
					if (!ObjectUtils.hasField(subClass,fldNm))
						fldNm = fldNm.toLowerCase();
					if (ObjectUtils.hasField(subClass,fldNm))
						r.setRelation("id",fldNm);
				}
			}
		}
	}

	private void _guessPKs()
	{
		Field idf = ObjectUtils.getField(type,"id");
		if (null != idf)
		{
			mapping.get(idf).setPK(true);
			this.pks = null; // clear pks
		}
	}

	private void _checkPKField(FieldRule r) throws IIIException
	{
		if (!(ObjectUtils.isInteger(r.getField().getType()) || ObjectUtils.isChildOf(r.getField()
				.getType(),CharSequence.class)))
			throw new IIIException("field can not be PK '" + r.getField().getName() + "'");
		else if (r.isFake())
			throw new IIIException("'" + type.getName() + "' field '" + r.getField().getName()
					+ "' can not be fake and pk at same time!");
		else if (!r.isSearchable())
			throw new IIIException("'" + type.getName() + "' field '" + r.getField().getName()
					+ "' must be searchable!");
	}

	private FieldRule[] getPKs() throws IIIException
	{
		if (this.pks == null)
		{
			synchronized (this)
			{
				if (pks == null)
				{
					List<FieldRule> frs = new LinkedList<FieldRule>();
					Iterator<FieldRule> it = mapping.values().iterator();
					while (it.hasNext())
					{
						FieldRule f = it.next();
						if (f.isPK())
							frs.add(f);
					}
					this.pks = new FieldRule[frs.size()];
					frs.toArray(this.pks);
				}
			}
		}
		return this.pks;
	}

	/**
	 * It will evaluate all XML & String part mapping configuration
	 * for each field of one object.
	 * 
	 * @param type: the object type
	 * @throws IIIException
	 */
	private void initMappings(Class type) throws IIIException
	{
		mapping = new HashMap<Field, FieldRule>();
		ignoreNull = (null != root && root.hasAttribute("ignore-null") ? Boolean.parseBoolean(root
				.getAttribute("ignore-null")) : true);
		/*
		 * Loop every field in of this type, if error rised, throw BeanExeption out
		 */
		Field[] fs = ObjectUtils.getAllMyStdFields(type);
		try
		{
			for (int i = 0; i < fs.length; i++)
			{
				Field f = fs[i];
				FieldRule r = this.getFieldRule(f);
				if (null == r)
				{
					r = new FieldRule(this,f);
					mapping.put(f,r);
				}
				r.evalNormalPart();
				r.evalXmlPart(fs.length);
			}
		}
		catch (XPathExpressionException e)
		{
			log.error(ExceptionUtils.dump(e));
		}
		List<FieldRule> frs = new LinkedList<FieldRule>();
		Iterator<FieldRule> it = mapping.values().iterator();
		while (it.hasNext())
		{
			FieldRule f = it.next();
			if (f.isComponentType())
				frs.add(f);
		}
		this.componentFields = new FieldRule[frs.size()];
		frs.toArray(this.componentFields);
	}

	public String getFetchSQL(Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		checkDBRule();
		StringBuffer sql = new StringBuffer();
		try
		{
			CharSequence condition = makeSQLCondition(obj,actived,locked);
			// make query field
			StringBuffer names = new StringBuffer();
			Iterator<FieldRule> it = mapping.values().iterator();
			while (it.hasNext())
			{
				FieldRule r = it.next();
				if (r.isIgnore(actived,locked))
					continue;
				if (r.isFake())
					continue;
				if (names.length() > 0)
					names.append(',');
				names.append(r.getNameInDB());
			}
			if (names.length() == 0)
				throw new IIIException("FetchSQL field names can not by empty!");

			sql.append("SELECT ");
			sql.append(names);
			sql.append(" FROM ");
			sql.append(this.getTableName(obj));
			sql.append(condition);
			sql.append(';');
			return sql.toString();
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
	}

	public String getDeleteSQL(Object obj) throws IIIException
	{
		checkDBRule();
		StringBuffer sql = new StringBuffer();
		CharSequence condition = makeSQLCondition(obj,null,null);
		sql.append("DELETE FROM ");
		sql.append(getTableWriteName(obj));
		sql.append(condition);
		sql.append(';');
		return sql.toString();
	}

	public CharSequence makeSQLCondition(Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		CharSequence condition = makeSQLConditionByPKs(obj,actived,locked);
		if (StringUtils.isBlank(condition))
			condition = this.makeSQLConditionByObject(obj,actived,locked);
		if (StringUtils.isBlank(condition))
			throw new IIIException("Condition of DeleteSQL can not by empty!");
		return condition;
	}

	public SQLFilter getObjectFilterByIds(Class type, String ids, Object refer) throws IIIException
	{
		checkDBRule();
		if (StringUtils.isBlank(ids))
			throw new IIIException("Dangerous! ids is null when delete by ids!");
		Field f = ObjectUtils.getField(type,"id");
		if (null == f)
			throw new IIIException("Expect 'id' field in object '" + type.getName() + "'");
		FieldRule r = mapping.get(f);
		if (r.isFake())
			throw new IIIException("'id' field didn't exist in table <" + getTableName(refer) + ">");
		if (ids.endsWith(","))
			ids = ids.substring(0,ids.length() - 1);
		return new IdsSQLObjectFilter(r.getNameInDB() + " IN (" + ids + ")");
	}

	private void checkDBRule() throws IIIException
	{
		if (!isDbDone)
			throw new IIIException("please init DB rule at first");
	}

	public FieldRule getFieldRule(Field f)
	{
		return mapping.get(f);
	}

	private CharSequence makeSQLConditionByPKs(Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		StringBuffer sb = new StringBuffer();
		StringBuffer condition = new StringBuffer(" WHERE ");
		FieldRule[] pks = this.getPKs();
		for (int i = 0; i < pks.length; i++)
		{
			FieldRule r = pks[i];
			if (r.isIgnore(actived,locked))
				continue;
			Object v = ObjectUtils.getFieldValue(obj,r.getField());
			if (null == v
					|| (ObjectUtils.isInteger(v.getClass()) && ((Integer)v).intValue() <= III.DEFAULT_OBJECT_ID))
				continue;
			if (sb.length() > 0)
				sb.append(" AND ");
			sb.append(r.getNameInDB());
			sb.append('=');
			sb.append(makeSQLFieldValue(r.getField(),v));
		}

		if (sb.length() > 0)
			return condition.append(sb);
		return null;
	}

	private CharSequence makeSQLConditionByObject(Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		StringBuffer sb = new StringBuffer();
		StringBuffer condition = new StringBuffer(" WHERE ");
		Class type = obj.getClass();
		Field[] fields = ObjectUtils.getAllMyStdFields(type);
		for (int i = 0; i < fields.length; i++)
		{
			Field f = fields[i];
			Object v = ObjectUtils.getFieldValue(obj,f);
			if (null == v)
				continue;
			FieldRule r = this.getFieldRule(f); // I think it will get a rule alwaysly!
			if (r.isFake() || !r.isSearchable() || r.isIgnore(actived,locked))
				continue;
			if (v.toString().equalsIgnoreCase(r.getIgnoreBy()))
				continue;
			if (ObjectUtils.isInteger(f.getType()))
			{
				int ii = ((Integer)v).intValue();
				if (r.isPK())
					if (ii <= III.DEFAULT_OBJECT_ID)
						continue;
			}

			sb.append(" AND ");
			sb.append(r.getNameInDB());
			sb.append('=');
			sb.append(makeSQLFieldValue(f,v));
		}

		if (sb.length() > 0)
			condition.append(sb.substring(5));
		else
			throw new IIIException("Condition of FetchSQL/UpdateSQL/DeleteSQL can not by empty!");
		return condition;
	}

	private CharSequence makeSQLFieldValue(Field f, Object v)
	{
		if (null == v)
			return "NULL";
		if (SQLUtils.isNotNeedQuote(f.getType()))
			return v.toString();
		else if (ObjectUtils.isBoolean(f.getType()))
			return ((Boolean)v).booleanValue() ? "'1'" : "'0'";
		else
			return "'" + SQLUtils.escapeFieldValue(ObjectUtils.castObjectToCharSequence(v)) + "'";
	}

	public String getAutoIncrementsSQL(Object refer) throws IIIException
	{
		StringBuffer sql = new StringBuffer();
		// make query field
		StringBuffer names = new StringBuffer();
		Iterator<FieldRule> it = mapping.values().iterator();
		while (it.hasNext())
		{
			FieldRule r = it.next();
			if (r.isFake())
				continue;
			if (!r.isAutoIncrement())
				continue;
			if (names.length() > 0)
				names.append(',');
			names.append("MAX(");
			names.append(r.getNameInDB());
			names.append(") AS ");
			names.append(r.getNameInDB());
		}
		if (names.length() == 0)
			throw new IIIException("FetchSQL field names can not by empty!");

		sql.append("SELECT ");
		sql.append(names);
		sql.append(" FROM ");
		sql.append(getTableName(refer));
		sql.append(';');
		return sql.toString();
	}

	public String getQuerySQL(Filter filter, FieldFilter actived, FieldFilter locked, Object refer)
			throws IIIException
	{
		StringBuffer sql = new StringBuffer();
		// make query field
		StringBuffer names = new StringBuffer();
		Iterator<FieldRule> it = mapping.values().iterator();
		while (it.hasNext())
		{
			FieldRule r = it.next();
			if (r.isIgnore(actived,locked))
				continue;
			if (r.isFake())
				continue;
			if (names.length() > 0)
				names.append(',');
			names.append(r.getNameInDB());
		}
		if (names.length() == 0)
			throw new IIIException("QuerySQL field names can not by empty!");

		sql.append("SELECT ");
		sql.append(names);
		sql.append(" FROM ");
		sql.append(getTableName(refer)).append(' ');
		if (null != filter)
		{
			String fs = StringUtils.trim(filter.getString());
			if (!StringUtils.isBlank(fs))
				if (fs.startsWith("WHERE") || fs.startsWith("ORDER BY"))
					sql.append(' ').append(fs);
				else
					sql.append(" WHERE ").append(fs);
		}
		sql.append(';');
		return sql.toString();
	}

	public String getInsertSQL(Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		StringBuffer names = new StringBuffer();
		StringBuffer values = new StringBuffer();
		Iterator<FieldRule> it = mapping.values().iterator();
		while (it.hasNext())
		{
			FieldRule r = it.next();
			if (r.isIgnore(actived,locked))
				continue;
			if (r.isFake() || r.isAutoIncrement() || !r.isWritable())
				continue;
			Object v;
			try
			{
				Method getter = ObjectUtils.getGetter(obj.getClass(),r.getField());
				v = getter.invoke(obj,(Object[])null);
				if (null == v)
					continue;
				if (names.length() > 0)
				{
					names.append(',');
					values.append(',');
				}
			}
			catch (Exception e)
			{
				throw new IIIException("Fail in field \"" + r.getField().getName() + "\"",e);
			}
			names.append(r.getNameInDB());
			values.append(makeSQLFieldValue(r.getField(),v));
		}
		StringBuffer sql = new StringBuffer();
		sql.append("INSERT INTO ");
		sql.append(getTableWriteName(obj));
		sql.append(" (");
		sql.append(names);
		sql.append(") VALUES(");
		sql.append(values);
		sql.append(");");
		return sql.toString();
	}

	public String getUpdateSQL(Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		StringBuffer pairs = new StringBuffer();
		try
		{
			CharSequence condition = makeSQLConditionByPKs(obj,actived,locked);
			if (StringUtils.isBlank(condition))
				throw new IIIException("Condition of UpdateSQL can not by empty!");
			Iterator<FieldRule> it = mapping.values().iterator();
			while (it.hasNext())
			{
				FieldRule r = it.next();
				if (r.isIgnore(actived,locked))
					continue;
				if (r.isFake() || !r.isWritable() || r.isPK() || r.isAutoIncrement())
					continue;

				Object v = ObjectUtils.getFieldValueByGetter(obj,r.getField());
				if (null == v && (ignoreNull || r.isNotNull()))
					continue;

				if (pairs.length() > 0)
					pairs.append(',');

				pairs.append(r.getNameInDB());
				pairs.append("=");
				pairs.append(makeSQLFieldValue(r.getField(),v));
			}
			StringBuffer sql = new StringBuffer();
			sql.append("UPDATE ");
			sql.append(this.getTableWriteName(obj));
			sql.append(" SET ");
			sql.append(pairs);
			sql.append(condition);
			sql.append(";");
			return sql.toString();
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
	}

	/**
	 * If use declare a key=true in &lt;field&gt;, the first one will be take as
	 * key field. Else, this function will try to find a key field from a list
	 * according the rule below:
	 * <ol>
	 * <li> If name field exsited, and it is a CharSequence, use it.
	 * <li> If key field exsited, and it is a CharSequance, use it.
	 * <li> In all CharSequance fields, find the shortest value in the list, use it.
	 * </ol>
	 * 
	 * @param list
	 * @return key field of the object, null mean this function can not find a key field
	 */
	public Field getKeyField(List list)
	{
		if (null != keyField)
			return keyField;
		// try to find "name" field
		if (null == keyField)
			keyField = ObjectUtils.getField(type,"name");

		// try to find "key" field
		if (null == keyField)
			keyField = ObjectUtils.getField(type,"key");

		// Evaluate by list
		if (null == keyField)
		{
			Map<Field, Object> candidates = new HashMap<Field, Object>();
			List<Field> pops = new LinkedList<Field>();
			Iterator it = list.iterator();
			while (it.hasNext())
			{
				Object o = it.next();
				Field[] fields = ObjectUtils.getAllMyStdFields(o.getClass());
				for (int i = 0; i < fields.length; i++)
				{
					Field f = fields[i];
					if (!f.getType().isPrimitive()
							&& ObjectUtils.isChildOf(f.getType(),CharSequence.class))
					{
						Object v = ObjectUtils.getFieldValue(o,f);
						if (candidates.containsKey(f))
						{
							if (v == null)
							{
								candidates.remove(f);
								if (ListUtils.findIndex(pops,f) < 0)
									pops.add(f);
							}
							else if (candidates.get(f).equals(v))
							{
								candidates.remove(f);
								if (ListUtils.findIndex(pops,f) < 0)
									pops.add(f);
							}
						}
						else if (v != null && ListUtils.findIndex(pops,f) < 0)
							candidates.put(f,v);
						else
							pops.add(f);
					}
				}
			}
			if (candidates.size() == 0)
				return null;
			if (candidates.size() == 1)
				keyField = candidates.keySet().iterator().next();
			else if (candidates.size() > 1) // try to find the most short value as the key
			{
				Iterator<Field> itf = candidates.keySet().iterator();
				keyField = itf.next();
				int len = candidates.get(keyField).toString().length();
				while (itf.hasNext())
				{
					Field f = itf.next();
					int l2 = candidates.get(f).toString().length();
					if (l2 < len)
					{
						keyField = f;
						len = l2;
					}
				}
			}
		}
		return keyField;
	}

	public Field getKeyField(Map map)
	{
		if (null != keyField)
			return keyField;
		List<Field> candidates = new LinkedList<Field>();

		Iterator it = map.keySet().iterator();
		while (it.hasNext())
		{
			Object key = it.next();
			Object o = map.get(key);
			Field[] fields = ObjectUtils.getAllMyStdFields(o.getClass());
			for (int i = 0; i < fields.length; i++)
			{
				Field f = fields[i];
				if (!f.getType().isPrimitive()
						&& ObjectUtils.isChildOf(f.getType(),CharSequence.class))
				{
					Object v = ObjectUtils.getFieldValue(o,f);
					if (null == v)
						continue;
					if (!f.getType().isPrimitive()
							&& ObjectUtils.isChildOf(f.getType(),CharSequence.class))
					{
						if (ListUtils.findIndex(candidates,f) < 0)
							if (key.equals(v))
								candidates.add(f);
					}

				}
			}
		}
		if (candidates.size() == 0)
			return null;
		if (candidates.size() == 1)
			keyField = candidates.get(0);
		else if (candidates.size() > 1) // try to find the most short value as the key
		{
			Iterator<Field> itf = candidates.iterator();
			keyField = itf.next();
			int len = keyField.getName().length();
			while (itf.hasNext())
			{
				Field f = itf.next();
				int l2 = f.getName().length();
				if (l2 < len)
				{
					keyField = f;
					len = l2;
				}
			}
		}
		return keyField;
	}

	/**
	 * Try to decide one object is new or not in database.
	 * it will find all PK fields, if all number fields eqaul 0, and all CharSequence
	 * fields is null, then take it as "new"
	 * @param obj
	 * @param refer
	 * 
	 * @return
	 * @throws IIIException 
	 */
	public boolean isNewInDB(Object obj) throws IIIException
	{
		checkDBRule();
		FieldRule[] ks = this.getNewKeys(obj);
		for (int i = 0; i < ks.length; i++)
		{
			Field f = ks[i].getField();
			Object v = ObjectUtils.getFieldValue(obj,f);
			if (null == v)
				continue;
			if (ObjectUtils.isInteger(f.getType()))
			{
				if (Long.parseLong(v.toString()) > III.DEFAULT_OBJECT_ID)
					return false;
			}
			else
				return false;
		}
		return true;
	}

	private FieldRule[] getNewKeys(Object refer)
	{
		if (this.newKeys == null)
		{
			synchronized (this)
			{
				if (newKeys == null)
				{
					List<FieldRule> frs = new LinkedList<FieldRule>();
					Iterator<FieldRule> it = mapping.values().iterator();
					while (it.hasNext())
					{
						FieldRule f = it.next();
						if (f.isNewKey())
							frs.add(f);
					}
					this.newKeys = new FieldRule[frs.size()];
					frs.toArray(this.newKeys);
					if (this.newKeys.length == 0 && this.pks.length > 0)
						this.newKeys = this.pks;
				}
			}
		}
		return this.newKeys;
	}

	public Class getComponentType(Field field)
	{
		return mapping.get(field).getComponentType();
	}

	public SegmentObjectFilter getDBFieldFilter(Field field)
	{
		FieldRule r = mapping.get(field);
		if (null == r.getFilter())
			return null;
		return new SegmentObjectFilter(r.getFilter().duplicate());
	}

	public SegmentObjectFilter getXPathFieldFilter(Field field)
	{
		// @ TODO ZZH: implement it by config item in <xml>
		return null;
	}

	public String getName()
	{
		return name;
	}

	public String getAlias(Field field)
	{
		return mapping.get(field).getAlias();
	}

	public String getDBFieldName(Field field)
	{
		return mapping.get(field).getNameInDB();
	}

	public String getDBFieldName(String name)
	{
		return getDBFieldName(ObjectUtils.getField(type,name));
	}

	public String getDomNodeName(Field field)
	{
		return mapping.get(field).getNameInDom();
	}

	public String getDomNodeName(String name)
	{
		return getDomNodeName(ObjectUtils.getField(type,name));
	}

	public int getDomNodeType(Field field)
	{
		return mapping.get(field).getDomNodeType();
	}

	public FieldRule[] getComponentFields()
	{
		return componentFields;
	}

	public boolean isHasComponentField()
	{
		return componentFields.length > 0;
	}

	public boolean isSupportAutoIncrement()
	{
		return getAutoIncrements().length > 0;
	}

	private boolean isHasPKs() throws IIIException
	{
		return this.getPKs().length > 0;
	}

	public FieldRule[] getAutoIncrements()
	{
		if (autoIncrements == null)
		{
			synchronized (this)
			{
				if (autoIncrements == null)
				{
					List<FieldRule> frs = new LinkedList<FieldRule>();
					Iterator<FieldRule> it = mapping.values().iterator();
					while (it.hasNext())
					{
						FieldRule f = it.next();
						if (f.isAutoIncrement())
							frs.add(f);
					}
					this.autoIncrements = new FieldRule[frs.size()];
					frs.toArray(this.autoIncrements);
				}
			}
		}
		return autoIncrements;
	}

	public Collection<FieldRule> rules()
	{
		return mapping.values();
	}
}
